<?php
/**
 * Created by PhpStorm.
 * User: 85210755@qq.com
 * NickName: 柏宇娜
 * Date: 2018/6/4 15:17
 */
include_once './vendor/autoload.php';

use think\Db;
use think\Cache;
use think\Log;
use app\Im;

class Server
{
    protected $ws;
    protected $im;
    public $log;

    public function __construct()
    {
        $config = include_once './config.php';
        if ($config['debug'] === false) {
            error_reporting(0);
        }
        ini_set('date.timezone', 'Asia/Shanghai');
        $this->ws = new swoole_websocket_server($config['swoole']['host'], $config['swoole']['port'],SWOOLE_PROCESS, SWOOLE_SOCK_TCP | SWOOLE_SSL);
        $this->ws->set($config['swoole']);
        //热重载
        $this->ws->on('WorkerStart', function ($server, int $worder_id) use ($config) {
            include_once './common.php';
            Cache::init($config['cache']);
            Db::setConfig($config['database']);
            $this->log = new Log;
            $this->log->init($config['log']);
            $this->im = new Im($this->ws, $this->log, $config['save_time']);
            Cache::clear('login');//开机清除在线标识缓存
        });
        $this->ws->on('Handshake', function (\swoole_http_request $request, \swoole_http_response $response) use ($config) {
            if (is_null($request->header['sec-websocket-protocol'])) {
                $response->end();
                return false;
            }

            // websocket握手连接算法验证
            $secWebSocketKey = $request->header['sec-websocket-key'];
            $patten          = '#^[+/0-9A-Za-z]{21}[AQgw]==$#';
            if (0 === preg_match($patten, $secWebSocketKey) || 16 !== strlen(base64_decode($secWebSocketKey))) {
                $response->end();
                return false;
            }

            $key = base64_encode(sha1(
                $request->header['sec-websocket-key'] . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11',
                true
            ));

            $headers = [
                'Upgrade'               => 'websocket',
                'Connection'            => 'Upgrade',
                'Sec-WebSocket-Accept'  => $key,
                'Sec-WebSocket-Version' => '13',
            ];

            if (isset($request->header['sec-websocket-protocol'])) {
                try {
                    $from = $request->header['sec-websocket-protocol'];
                    if (strpos($from, '_')) {
                        list($from, $to) = explode('_', $from);
                    }
                    $uid   = explode('-', $from);
                    $field = ['user' => 'user_id', 'seller' => 'id', 'admin' => 'id'];
                    $user  = Db::name($uid[0])->where($field[$uid[0]], $uid[1])->cache(true)->count();
                    if (!$user) {
                        throw new Exception('用户不存在~');
                    }
                    //FIXME 记录IP地址 $request->server['remote_addr']
                    Cache::tag('login')->set($request->fd, $from);
                    Cache::tag('login')->set($from, $request->fd);
                    //TODO 异步任务清理保留超过15天消息
                    $headers['Sec-WebSocket-Protocol'] = $request->header['sec-websocket-protocol'];
                } catch (Exception $e) {
                    $response->end();
                    return false;
                }
            }

            foreach ($headers as $key => $val) {
                $response->header($key, $val);
            }

            $response->status(101);
            $response->end();
            $this->ws->defer(function () use ($request) {
                $this->ws->task(['type' => 'init', 'data' => [$request->fd, $request->header['sec-websocket-protocol']]]);
            });
        });

        $this->ws->on('Message', [$this, 'message']);
        $this->ws->on('Task', [$this, 'task']);
        $this->ws->on('Finish', [$this, 'finish']);
        $this->ws->on('Close', [$this, 'close']);
        $this->ws->on('Shutdown', [$this, 'shutdown']);
        $this->ws->start();
    }

    public function message($ws, $frame)
    {
        try {
            $frame->data = json_decode($frame->data, true);
            $this->im->handle($frame);
        } catch (\Exception $e) {
            $this->log->error('onMessage::' . $e->getMessage());
            $ws->push($frame->fd, json($e->getMessage(), 'error'));
        }
    }

    public function task($ws, $task_id, $from_id, $data)
    {
        switch ($data['type']) {
            case 'init':
                $notice = $this->im->get_list($data['data'], true);
                //上线通知好友
                foreach ($notice['fds'] as $fd) {
                    if ($fd > 0)
                        $ws->push($fd, json($notice['online_user'], 'success', 'online_notice'));
                }
                break;
            case 'offline_notice':
                //上线通知好友
                foreach ($data['fds'] as $fd) {
                    if ($fd > 0)
                        $ws->push($fd, json($data['offline_user'], 'success', 'offline_user'));
                }
                break;
            case 'im_push':
                foreach ($data['fds'] as $fd) {
                    if ($fd > 0)
                        $ws->push($fd, json($data['data']));
                }
                if (isset($data['finish'])) {
                    $this->im->finish($data['finish']);
                }
                break;
            default:
                $this->log->error('task::' . var_export($data));
        }

    }

    public function finish($ws, $task_id, $data)
    {
        echo '发送完成' . PHP_EOL;
    }

    public function close($ws, $fd)
    {
        $user = Cache::tag('login')->pull($fd);
        Cache::tag('login')->rm($user);
        $list = Db::name('im_msg')->where('__from|__to', '=', $user)->order('id DESC')->group('__from,__to')->field('__from,__to')->select();
        if ($list) {
            $list = array_unique(array_merge(array_column($list, '__from'), array_column($list, '__to')));
            $temp = array_search($user, $list);
            if ($temp !== false) {
                unset($list[$temp]);
            }
            $list = array_values($list);
            $fds  = [];
            foreach ($list as $index => $to) {
                $to_user = Cache::tag('login')->get($to);
                if ($to_user) {
                    $fds[] = $to_user;
                }
            }
            if (count($fds)) {
                $ws->task(['type' => 'offline_notice', 'fds' => $fds, 'offline_user' => $user]);
            }
        }
    }

    //清除在线标识缓存
    public function shutdown($ws)
    {
        Cache::clear('login');
    }
}
